import mino from "@/data/mino.json"
import _ from "lodash"
import { useRafFn, useEventListener } from '@vueuse/core'
import dayjs from "@/utils/dayjs.ts";
import {
    getKeyBoard,
} from "@/utils/config"
import { baseUrl } from "@/utils/env";
class Bag {

    list: Array<any> = []
    minoTypes: Array<string> = []
    targetIndex: number = -1
    targetHold: any = ''
    bagRule = '7Bag'
    showNextNumber = 5

    constructor() {
        this.minoTypes = mino.filter((i: any) => i.type != 'rubbish' && i.type != 'build').map((i: any) => i.type)
        this.init()
    }

    init(tetrisInfo: any = {}) {
        if (tetrisInfo) {
            this.bagRule = tetrisInfo.bagRule
            this.showNextNumber = tetrisInfo.showNextNumber
        }
        // 初始化
        this.list = []
        this.targetIndex = -1 // 在初始化后 必须先调用一次next 再获取当前方块
        this.targetHold = ''
        this.add()
    }

    add() {
        if (this.bagRule == '7Bag') {
            let list = this.minoTypes
            list = _.shuffle(list) // 洗牌
            this.list.push(...list)
        }
        else if (this.bagRule == 'random') { // 随机方块袋
            for (let index = 0; index < 7; index++) {
                let random = _.random(0, 6)
                let item = this.minoTypes[random]
                this.list.push(item)
            }
        }
    }

    next() {
        this.targetIndex++
        if (this.targetIndex + 7 >= this.list.length) this.add()
    }

    hold() {
        if (this.targetHold) {
            const temp = this.targetHold
            this.targetHold = this.list[this.targetIndex]
            this.list[this.targetIndex] = temp
        }
        else {
            this.targetHold = this.list[this.targetIndex]
            this.next()
        }
    }

    get targetMino() {
        const type = this.list[this.targetIndex]
        const minoItem = mino.find((i: any) => i.type === type)
        return minoItem
    }

    get nextMinoList() {
        return this.list.slice(this.targetIndex + 1, this.targetIndex + 1 + this.showNextNumber)
    }
}

const rotateRule = { //旋转规则 O与I为特殊情况 无需旋转
    L: { //J型
        0: [[-1, -1], [-1, 0], [1, 0]], //0度 旋转中心之外的三个方块 以旋转中心为原点的坐标
        90: [[1, -1], [0, -1], [0, 1]], //90度
        180: [[1, 1], [1, 0], [-1, 0]], //180度
        270: [[0, -1], [0, 1], [-1, 1]] //270度
    },
    J: { //L型
        0: [[-1, 0], [1, 0], [1, -1]], //0度
        90: [[0, -1], [0, 1], [1, 1]], //90度
        180: [[1, 0], [-1, 0], [-1, 1]], //180度
        270: [[0, 1], [0, -1], [-1, -1]] //270度
    },
    S: { //S型
        0: [[-1, 0], [0, -1], [1, -1]], //0度
        90: [[0, -1], [1, 0], [1, 1]], //90度
        180: [[1, 0], [0, 1], [-1, 1]], //180度
        270: [[0, 1], [-1, 0], [-1, -1]] //270度
    },
    Z: { //Z型
        0: [[-1, -1], [0, -1], [1, 0]], //0度
        90: [[1, -1], [1, 0], [0, 1]], //90度
        180: [[1, 1], [0, 1], [-1, 0]], //180度
        270: [[-1, 1], [-1, 0], [0, -1]] //270度
    },
    T: { //T型
        0: [[-1, 0], [0, -1], [1, 0]], //0度
        90: [[0, -1], [1, 0], [0, 1]], //90度
        180: [[1, 0], [0, 1], [-1, 0]], //180度
        270: [[0, 1], [-1, 0], [0, -1]] //270度
    }
} as any

const getKickWallRule = (type = 'T', spinBack = false) => {
    if (type == 'I') {
        return {
            90: [[-2, 0], [1, 0], [-2, 1], [1, -2]], //90度
            180: [[-1, 0], [2, 0], [-1, -2], [2, 1]], //180度
            270: [[2, 0], [-1, 0], [2, -1], [-1, 2]], //270度
            0: [[1, 0], [-2, 0], [1, 2], [-2, -1]] //0度
        }
    }
    else {
        if (spinBack) {
            return {
                90: [[-1, 0], [-1, -1], [0, 2], [-1, 2]], //90度 2 => right
                180: [[-1, 0], [-1, 1], [0, -2], [-1, -2]], //180度 left => 2
                270: [[1, 0], [1, -1], [0, 2], [1, 2]], //270度 0 => left
                0: [[1, 0], [1, 1], [0, -2], [1, -2]], // right => 0
            }
        }
        else {
            return {
                90: [[-1, 0], [-1, -1], [0, 2], [-1, 2]], //90度
                180: [[1, 0], [1, 1], [0, -2], [1, -2]], //180度
                270: [[1, 0], [1, -1], [0, 2], [1, 2]], //270度
                0: [[-1, 0], [-1, 1], [0, -2], [-1, -2]],
            }
        }
    }
}

const getAfterSpinBlock = (target: Array<any> = [], fixed: Array<any> = [], deg = 0, spinBack = false) => {
    let result = [] as any
    const type = target[0].type
    if (type == 'O') result = target
    else if (type == 'I') result = getAfterSpinIBlock(target, deg)
    else {
        const rule = rotateRule[type][deg]
        const center = target[0]
        result = target.map((i, index) => {
            if (index == 0) return i
            else {
                const [x, y] = rule[index - 1]
                return {
                    ...i,
                    x: center.x + x,
                    y: center.y + y
                }
            }
        })
    }
    const isOk = checkTargetBlock(result, fixed)
    if (isOk) return result
    else {
        const afterKickWallBlock = blockKickWallTest(result, fixed, deg, spinBack)
        if (afterKickWallBlock) return afterKickWallBlock // 踢墙成功 使用踢墙后的位置
        else return false // 踢墙失败 旋转失败
    }
}

// 检测当前块是否在合理的位置
const checkTargetBlock = (result: Array<any> = [], fixed: Array<any> = []) => {
    return result.every(i => {
        return i.x >= 0 && i.x < 10 && i.y < 20 && !fixed.some(j => j.x == i.x && j.y == i.y)
    })
}

// 获取I方块旋转后的位置
const getAfterSpinIBlock = (target: Array<any> = [], deg = 0) => {
    if (deg == 90) {
        //取第二个方块为中心点
        const { x, y } = target[2]
        return target.map((i, index) => {
            i.x = x
            if (index == 0) i.y = y - 1
            if (index == 1) return i
            if (index == 2) i.y = y + 1
            if (index == 3) i.y = y + 2
            return i
        })
    }
    if (deg == 180) {
        //取第二个方块为中心点
        const { x, y } = target[2]
        return target.map((i, index) => {
            i.y = y
            if (index == 0) i.x = x - 2
            if (index == 1) i.x = x - 1
            if (index == 2) return i
            if (index == 3) i.x = x + 1
            return i
        })
    }
    if (deg == 270) {
        //取第二个方块为中心点
        const { x, y } = target[2]
        return target.map((i, index) => {
            i.x = x
            if (index == 0) i.y = y - 2
            if (index == 1) i.y = y - 1
            if (index == 2) return i
            if (index == 3) i.y = y + 1
            return i
        })
    }
    if (deg == 0) {
        //取第二个方块为中心点
        const { x, y } = target[2]
        return target.map((i, index) => {
            i.y = y
            if (index == 0) i.x = x - 1
            if (index == 1) return i
            if (index == 2) i.x = x + 1
            if (index == 3) i.x = x + 2
            return i
        })
    }
}

// 方块旋转后位置超出边界或与固定块重叠 则进行踢墙测试
const blockKickWallTest = (target: Array<any> = [], fixed: Array<any> = [], deg: number, spinBack = false) => {
    const type = target[0].type
    const ruleList = getKickWallRule(type, spinBack) as any
    const rule = ruleList[deg]
    let isOk = false
    let testList = []
    for (let index = 0; index < rule.length; index++) {
        const ruleItem = rule[index]
        let afterKickWallBlock = target.map((i: any) => {
            return {
                ...i,
                x: i.x + ruleItem[0],
                y: i.y + ruleItem[1]
            }
        })
        testList.push(afterKickWallBlock)
        isOk = checkTargetBlock(afterKickWallBlock, fixed)
        if (isOk) return afterKickWallBlock
    }
    if (!isOk) return false
}

// 消行判定
const checkClearLine = (fixed: Array<any> = [], target: Array<any> = [], lastAction = '') => {
    let _target = JSON.parse(JSON.stringify(target))
    const resultFixed = [] as any
    let formatList = [] as any
    fixed.forEach((i: any) => {
        const hasIndex = formatList.findIndex((j: any) => j.y == i.y)
        if (hasIndex == -1) {
            formatList.push({
                y: i.y,
                list: [{ ...i }]
            })
        }
        else formatList[hasIndex].list.push({ ...i })
    })
    // 按y从大到小排序
    formatList.sort((a: any, b: any) => b.y - a.y)
    // 消行
    let hasCanClearLine = true
    let hasClearLine = 0 // 消行数
    let hasClearRubbishLine = 0 // 消除垃圾行数
    let clearLineY = 0
    while (hasCanClearLine) {
        const hasCanClearLineIndex = formatList.findIndex((i: any) => i.list.length >= 10)
        if (hasCanClearLineIndex == -1) hasCanClearLine = false
        else {
            let isRubbishLine = false
            hasClearLine++
            clearLineY = formatList[hasCanClearLineIndex].y
            formatList[hasCanClearLineIndex].list.map((i: any) => { // 消除的行是否存在垃圾
                if (i.type == 'rubbish') isRubbishLine = true
            })
            if (isRubbishLine) hasClearRubbishLine++ // 记录消除垃圾行数
            formatList = formatList.filter((i: any) => i.y != clearLineY) // 删除消行的行
            formatList = formatList.map((i: any) => { // 消除行上方的方块依次下移
                if (i.y < clearLineY) {
                    i.y = i.y + 1
                    i.list = i.list.map((j: any) => {
                        j.y = i.y
                        return j
                    })
                }
                return i
            })
        }
    }
    if (hasClearLine) {
        let hasTSpin = false
        formatList.map((line: any) => {
            line.list.map((block: any) => {
                resultFixed.push(block)
            })
        })
        if (_target[0].type == 'T') {
            const center = _target[0] // t块的旋转中心
            const checkLocation = [ // 旋转中心的四个对角的位置
                [center.x - 1, center.y - 1],
                [center.x - 1, center.y + 1],
                [center.x + 1, center.y - 1],
                [center.x + 1, center.y + 1]
            ]
            // Tspin规则
            // 1. 满足T块对角线处四个块 存在3个以上的固定块
            let checkNum = 0 // 对角存在固定块的个数
            checkLocation.map((i: any) => {
                const hasIndex = fixed.findIndex((j: any) => j.x == i[0] && j.y == i[1])
                const isWall = i[0] <= 0 || i[0] >= 9 || i[1] >= 19
                if (hasIndex != -1 || isWall) {
                    checkNum++
                }
            })
            console.log('Tspin判断 1: 对角存在3个以上的固定块', checkNum >= 3)
            // 2. 消行前 方块不可再上下移动
            let target1 = _target.map((i: any) => { return { ...i, y: i.y - 1 } })
            let target2 = _target.map((i: any) => { return { ...i, y: i.y + 1 } })
            let canMove = checkTargetBlock(target1, fixed) || checkTargetBlock(target2, fixed)
            console.log('Tspin判断 2: 消行前 方块不可再上下移动', !canMove)
            // 3.最后一步非硬降操作为旋转 不为左右移动或重力下降
            if (checkNum >= 3 && !canMove && lastAction == 'spin') {
                hasTSpin = true
            }
            console.log('Tspin判断 3: 最后一步非硬降操作为旋转 不为左右移动或重力下降', lastAction == 'spin', 'lastAction', lastAction)
        }
        return {
            result: resultFixed, // 消行后的固定块
            hasTSpin, // 是否Tspin
            hasClearLine, // 消行数
            hasClearRubbishLine, // 消除垃圾行数
            clearLineY // 最后消行号
        }
    }
    else return {
        result: false
    }
}

const isMobile = () => {
    return window.navigator.userAgent.match(
        /(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i) ? true : false
}

const arrayRange = (start: number, stop: number, step = 1) =>
    Array.from(
        { length: (stop - start) / step + 1 },
        (value, index) => start + index * step
    )

class RafTimer {
    private timer = null as any // pause, resume
    private backupTimer = null as any
    time = 0 // 累计时间 毫秒
    private startTime = 0 // 开始时间 毫秒时间戳
    private status = false // 是否在运行
    private cb: any = () => { } // 回调函数

    constructor() {
        this.init()
    }

    init() {
        if (this.timer) this.timer.pause()
        this.timer = null
        this.time = 0
        this.startTime = 0
        this.status = false
        this.timer = useRafFn(() => { this.run() }, { immediate: false }) // 不立即执行 每秒最多执行30次
    }

    private run() {
        const duration = dayjs().valueOf() - this.startTime // 时长
        this.time += duration
        this.startTime = dayjs().valueOf()
        this.cb(this.time)
    }

    setCb(cb: any) {
        this.cb = cb
    }

    start() {
        if (this.status) return
        this.status = true
        if (this.startTime == 0) this.startTime = dayjs().valueOf()
        else this.startTime = dayjs().valueOf()
        this.timer.resume()
    }

    pause() {
        if (!this.status) return
        this.status = false
        this.timer.pause()
    }

    clear() {
        this.pause()
        this.timer = null
    }

    // 页面可见性变化
    pageVisibilityStateChange(pageShow = true) {
        if (!pageShow) { // 页面不可见 raf计时器无法工作 启用备用计时器
            if (this.status) {
                this.enableBackupTimer()
                this.timer.pause()
            }
        }
        else { // 页面可见 关闭备用计时器
            this.disableBackupTimer()
            if (this.status) this.timer.resume()
        }
    }

    // 启用备用计时器
    enableBackupTimer() {
        if (!this.backupTimer) { // 备用计时器未启用
            this.disableBackupTimer()
            this.backupTimer = setInterval(() => {
                this.run()
            }, 17)
        }
    }

    // 关闭备用计时器
    disableBackupTimer() {
        if (this.backupTimer) {
            clearInterval(this.backupTimer)
            this.backupTimer = null
        }
    }

    get rafTime() {
        return this.time
    }

    get timeFormat() {
        return dayjs.duration(this.time).format('mm:ss:SSS')
    }
}

class KeyBoardControl {
    mode = ''
    private removeEventListenerKeydown = () => { } // 移除按下监听
    private removeEventListenerKeyup = () => { } // 移除松开监听
    action = {} as any // 回调函数集合

    private removeListener() {
        this.removeEventListenerKeydown()
        this.removeEventListenerKeyup()
        this.removeEventListenerKeydown = () => { }
        this.removeEventListenerKeyup = () => { }
    }

    // loadAction 加载键盘操作回调
    // init 根据当前场景 加载相应的键位 触发相应的回调函数
    // 调用init前 需加载所需回调函数

    loadAction(action = {}) {
        this.action = action
    }

    init(mode = '') {
        this.mode = mode
        // 移除所有监听 重新挂载
        this.removeListener()
        if (mode == 'game') {
            // 用户自定义键位设置
            const userKeyBoardConfig = getKeyBoard()
            // 监听键盘按下
            this.removeEventListenerKeydown = useEventListener(document, 'keydown', (e) => {
                const code = e.code
                Object.keys(userKeyBoardConfig).forEach(actionName => {
                    const hasAction = code == userKeyBoardConfig[actionName]
                    if (hasAction) { // 按下对应的键位
                        if (actionName == 'hardDown') this.action.actionHardDown()
                        if (actionName == 'left') this.action.actionLeft()
                        if (actionName == 'right') this.action.actionRight()
                        if (actionName == 'softDown') this.action.actionSortDown()
                        if (actionName == 'hold') this.action.actionHold(e)
                        if (actionName == 'pause') this.action.actionPause(e)
                        if (actionName == 'rotate') this.action.actionSpin()
                        if (actionName == 'rotateBack') this.action.actionSpinBack()
                    }
                })
            })
            // 监听键盘松开
            this.removeEventListenerKeyup = useEventListener(document, 'keyup', (e) => {
                const code = e.code
                Object.keys(userKeyBoardConfig).forEach(actionName => {
                    const hasAction = code == userKeyBoardConfig[actionName]
                    if (hasAction) { // 松开对应的键位
                        if (actionName == 'hardDown') this.action.actionHardDownEnd()
                        if (actionName == 'left') this.action.actionLeftEnd()
                        if (actionName == 'right') this.action.actionRightEnd()
                        if (actionName == 'softDown') this.action.actionSortDownEnd()
                        if (actionName == 'hold') this.action.actionHoldEnd()
                        if (actionName == 'rotate') this.action.actionSpinEnd()
                        if (actionName == 'rotateBack') this.action.actionSpinBackEnd()
                    }
                })
            })
        }
        else if (mode == 'pause') {
            // 用户自定义键位设置
            const userKeyBoardConfig = getKeyBoard()
            // 监听键盘按下
            this.removeEventListenerKeydown = useEventListener(document, 'keydown', (e) => {
                const code = e.code
                if (code == userKeyBoardConfig.pause) this.action.actionPause(e)
            })
        }
        else {
            if (this.action.actionHardDownEnd) this.action.actionHardDownEnd()
            if (this.action.actionLeftEnd) this.action.actionLeftEnd()
            if (this.action.actionRightEnd) this.action.actionRightEnd()
            if (this.action.actionSortDownEnd) this.action.actionSortDownEnd()
            if (this.action.actionHoldEnd) this.action.actionHoldEnd()
            if (this.action.actionSpinEn) this.action.actionSpinEnd()
            if (this.action.actionSpinBackEnd) this.action.actionSpinBackEnd()
            return
        }
    }
}

const fullArray = (start: number, end: number) => {
    let arr = []
    for (let i = start; i <= end; i++) {
        arr.push(i)
    }
    return arr
}

// 超过2m 进行压缩
const compressImg = (file: File) => {
    return new Promise((resolve, reject) => {
        // 超过0.5m 进行压缩
        if (file.size > 0.5 * 1024 * 1024) {
            const reader = new FileReader();
            reader.readAsDataURL(file);
            reader.onerror = () => {
                reject(false)
            };
            reader.onload = (e: any) => {
                const img = new Image();
                img.src = e.target.result;
                img.onload = () => {
                    const canvas = document.createElement("canvas");
                    const ctx = canvas.getContext("2d") as any;
                    const width = img.width;
                    const height = img.height;
                    const maxWidth = 800;
                    const maxHeight = 800;
                    let scale = 1;
                    if (width > height) {
                        if (width > maxWidth) {
                            scale = maxWidth / width;
                        }
                    } else if (height > width) {
                        if (height > maxHeight) {
                            scale = maxHeight / height;
                        }
                    } else if (height > maxHeight && width > maxWidth) {
                        scale = Math.min(maxWidth / width, maxHeight / height);
                    }
                    canvas.width = width * scale;
                    canvas.height = height * scale;
                    ctx.drawImage(
                        img,
                        0,
                        0,
                        width,
                        height,
                        0,
                        0,
                        width * scale,
                        height * scale
                    );
                    canvas.toBlob(
                        (blob: any) => {
                            let formData = new FormData()
                            formData.append('file', blob, new Date().getTime() + '.png')
                            resolve(formData);
                        },
                        "image/jpeg",
                        0.8
                    );
                };
                reader.onerror = (e) => {
                    reject(e);
                };
            };
        } else {
            let formData = new FormData()
            formData.append('file', file, new Date().getTime() + '.png')
            resolve(formData)
        };
    });
};

const loadingImage = (src: string) => {
    // 加载图片路径
    return new Promise((resolve, reject) => {
        const img = new Image()
        img.src = src
        img.onload = () => {
            resolve(true)
        }
        img.onerror = () => {
            console.log('loadingImage 加载图片失败, src:', src);
            reject(false)
        }
    })
}

import { openDB, IDBPDatabase } from 'idb';
class BackgroundCache {
    db: IDBPDatabase | null = null;
    dbName: string;
    storeName: string;
    constructor() {
        this.dbName = 'bg-image-cache';
        this.storeName = 'images';
        this.initDB();
    }

    async initDB() {
        this.db = await openDB(this.dbName, 1, {
            upgrade(db) {
                db.createObjectStore('images');
            },
        });
    }

    // 存储图片 Blob
    async cacheImage(url: string, base64: string) {
        await this.db.put(this.storeName, base64, url);
    }

    // 获取缓存的图片 Blob
    async getCachedImage(url: string) {
        return await this.db.get(this.storeName, url);
    }

    // 检查是否有缓存
    async hasCache(url: string) {
        return (await this.getCachedImage(url)) !== undefined;
    }
}

const BgCache = new BackgroundCache(); // indexDB存储器

export {
    Bag,
    RafTimer,
    KeyBoardControl,
    BgCache,
    getAfterSpinBlock,
    checkClearLine,
    isMobile,
    arrayRange,
    fullArray,
    compressImg,
    loadingImage,
}